La herencia es una forma práctica de reutilizar el código en el mundo de la orientación a objetos. Usar herencia con JPA y OpenXava es tan fácil como hacerlo con puro Java. Vamos a usar la herencia para quitar el código repetitivo y aburrido, como la definición de los UUID. También, añadiremos una nueva entidad, Pedido, y usaremos la herencia para hacerlo con muy poco código. Además, aprenderás cuán práctico es usar herencia incluso para código de pruebas.
Heredar de una superclase mapeada
Las clases Autor, Categoria y Factura tienen algo de código en común. Este código es la definición del campo oid:
Este código es exactamente el mismo para todas estas clases. Ya sabes que copiar y pegar es un pecado mortal, por eso tenemos que buscar una forma de quitar este código repetido, y así evitar ir al infierno.
Una solución elegante en este caso es usar herencia. JPA permite varias formas de herencia. Una de ellas es heredar de una superclase mapeada. Una superclase mapeada es una clase Java con anotaciones de mapeo JPA, pero no es una entidad en sí. Su único objetivo es ser usada como clase base para definir entidades. Usemos una, y verás su utilidad rápidamente.
Primero, movemos el código común a una clase marcada como @MappedSuperclass. La llamamos Identificable:
packageorg.openxava.facturacion.modelo;importjavax.persistence.*;importorg.hibernate.annotations.*;importorg.openxava.annotations.*;
@MappedSuperclass // Marcada como una superclase mapeada en vez de como una entidadpublicclass Identificable {
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
@Column(length=32)privateString oid;// La definición de propiedad incluye anotaciones de OpenXava y JPApublicString getOid(){return oid;}publicvoid setOid(String oid){this.oid = oid;}}
Ahora puedes definir las entidades Autor, Categoria y Factura de una manera más sucinta. Para ver un ejemplo aquí tienes el nuevo código para Categoria:
packageorg.openxava.facturacion.modelo;importjavax.persistence.*;
@Entitypublicclass Categoria extends Identificable {// Extiende de Identificable// por tanto no necesita tener una propiedad id
@Column(length = 50)privateString descripcion;publicString getDescripcion(){return descripcion;}publicvoid setDescripcion(String descripcion){this.descripcion = descripcion;}}
La refactorización es extremadamente simple. Categoria ahora desciende de Identificable y hemos quitado el campo oid y los métodos getOid() y setOid(). De esta forma, no sólo tu código es más corto, sino también más elegante, porque estás declarando tu clase como identificable (el qué, no el cómo), y has quitado de tu clase de negocio un código que era un tanto técnico.
Ahora, puedes aplicar esta misma refactorización a las entidades Autor y Factura. Además, a partir de ahora extenderás la mayoría de tus entidades de la superclase mapeada Identificable.
Hemos creado nuestra propia clase Identificable para ver las ventajas de usar superclases mapeadas, sin embargo OpenXava te provee una clase Identifiable lista para usar que puedes encontrar en el paquete org.openxava.model. Por tanto, en tu próximo proyecto no has de escribir la clase Identificable otra vez, simplemente usa la incluida en OpenXava.
Has aprendido, pues, que una superclase mapeada es una clase normal y corriente con anotaciones de mapeo JPA que puedes usar como clase base para tus entidades. También has aprendido como usar una superclase mapeada para simplificar tu código.
Herencia de entidades
Una entidad puede heredar de otra entidad. Esta herencia de entidades es una herramienta muy útil para simplificar tu modelo. Vamos a usarla para añadir una nueva entidad, Pedido, a tu aplicación Facturacion.
Nueva entidad Pedido
Queremos añadir un nuevo concepto a la aplicación Facturacion: pedido. Mientras que una factura es algo que quieres cobrar a tu cliente, un pedido es algo que tu cliente te ha solicitado. Estos dos conceptos están fuertemente unidos, porque cobrarás por las cosas que tu cliente te ha pedido, y tú le has servido.
Sería interesante poder tratar pedidos en tu aplicación, y asociar estos pedidos con sus correspondientes facturas. Tal como muestra el siguiente diagrama UML:
Y con código Java:
@Entitypublicclass Factura {
@OneToMany(mappedBy="factura")privateCollection<Pedido> pedidos;
...
}
@Entitypublicclass Pedido {
@ManyToOne // Sin carga vaga (1)private Invoice invoice;
...
}
Es decir, una factura tiene varios pedidos, y un pedido puede referenciar a una factura. Fíjate como no usamos inicialización vaga (lazy fetching) para la referencia factura (1), esto es por un bug de Hibernate cuando la referencia
contiene la relación bidireccional (es decir, es la referencia declarada en el atributo mappedBy del @OneToMany).
¿Cómo es Pedido? Bien, tiene un cliente, unas líneas de detalle con producto y cantidad, un año y un número. Algo así como esto:
Curiosamente, este diagrama UML es idéntico al diagrama de Factura. Es decir, para crear tu entidad Pedido puedes copiar y pegar la clase Factura, y asunto zanjado. Pero, ¡espera un momento! ¿“Copiar y pegar” no era un pecado mortal? Hemos de encontrar una forma de reutilizar Factura para Pedido.
CommercialDocument como entidad abstracta
Una manera práctica de reutilizar el código de Factura para Pedido es usando herencia, además es una excusa perfecta para aprender lo fácil que es usar la herencia con JPA y OpenXava.
En la mayoría de las culturas orientadas a objetos has de observar el precepto sagrado es un. Esto significa que no podemos hacer que Factura herede de Pedido, porque una Factura no es un Pedido, y por la misma regla no podemos hacer que Pedido descienda de Factura. La solución para este caso es crear una clase base para ambos, Pedido y Factura. Llamaremos a esta clase DocumentoComercial.
Aquí tienes el diagrama UML para DocumentoComercial:
Y aquí tienes la misma idea expresada con Java:
Empecemos a refactorizar el código actual. Primero, renombra (usando Refactor > Rename) Factura como DocumentoComercial. Después, edita el código de DocumentoComercial para declararla como una clase abstracta, así:
abstractpublicclass DocumentoComercial // Añadimos el modificador abstract
Queremos crear instancias de Factura y Pedido, pero no queremos crear instancias de DocumentoComercial directamente, por eso la declaramos abstracta.
Factura refactorizada para usar herencia
Ahora, has de crear la entidad Factura extendiéndola de DocumentoComercial. Puedes ver el nuevo código de Factura:
Factura tiene ahora un código bastante breve, de hecho el cuerpo de la clase está, por ahora, vacío.
Este nuevo código necesita un esquema de base de datos ligeramente diferente, ahora las facturas y los pedidos se almacenarán en la misma tabla (la tabla DocumentoComercial) usando una columna discriminador. Por tanto has de borrar las viejas tablas ejecutando las siguientes sentencias SQL:
drop table factura_detalles;
drop table factura;
Para ejecutar estas sentencias SQL ejecuta start-manager-hsqldb.bat (o el .sh correspendiente si usas Linux/Mac) de openxava/tomcat/bin. Mostrará un diálogo de configuración, pon la URL de la base de datos (jdbc:hsqldb:hsql://localhost:1666) y acéptalo:
Después:
Ya puedes ejecutar el módulo Factura y verlo funcionando en tu navegador. Lanza también PruebaFactura. Tiene que salirte verde.
Crear Pedido usando herencia
Gracias a DocumentoComercial el código para Pedido es más sencillo que el mecanismo de un sonajero:
Después de escribir la clase Pedido, aunque de momento esté vacía, ya puedes usar el módulo Pedido desde tu navegador. Sí, a partir de ahora crear una nueva entidad con una estructura parecida a Factura, es decir cualquier entidad para un documento comercial, es simple y rápido. Un buen uso de la herencia es una forma elegante de tener un código más simple.
El módulo Pedido funciona perfectamente, pero tiene un pequeño problema. El nuevo número de pedido lo calcula a partir del último número de factura, no de pedido. Esto es así porque el calculador para el siguiente número lee de la entidad Factura. Un solución obvia es mover la definición de la propiedad numero de DocumentoComercial a Factura y Pedido. Aunque, no lo vamos a hacer así, porque en la siguiente lección refinaremos la forma de calcular el número de documento, de momento simplemente haremos un pequeño ajuste en el código actual para que no falle. Edita la clase CalculadorSiguienteNumeroParaAnyo y en la consulta cambia “Factura” por “DocumentoComercial”, dejando el método calculate(), así:
publicObject calculate()throwsException{Query query = XPersistence.getManager().createQuery("select max(f.numero) from " +
"DocumentoComercial f " + // DocumentoComercial en vez de Factura"where f.anyo = :anyo");
query.setParameter("anyo", anyo);Integer ultimoNumero = (Integer) query.getSingleResult();return ultimoNumero == null?1:ultimoNumero + 1;}
Ahora buscamos el número máximo de cualquier tipo de documento comercial del año para calcular el nuevo número, por lo tanto la numeración es compartida para todos los tipos de documentos. Esto lo mejoraremos en la siguiente lección para separar la numeración para facturas y pedidos; y para tener un mejor soporte de entornos multiusuario.
Convención de nombres y herencia
Fíjate que no has necesitado cambiar el nombre de ninguna propiedad de Factura para hacer la refactorización. Esto es por el siguiente principio práctico: No uses el nombre de clase en los nombres de miembro, por ejemplo, dentro de una clase Cuenta no uses la palabra “Cuenta” en ningún método o propiedad:
publicclass Cuenta {// No usaremos Cuenta en los nombres de los miembrosprivateint numeroCuenta;// Mal, porque usa “cuenta”privateint numero;// Bien, no usa “cuenta”publicvoid cancelarCuenta(){}// Mal, porque usa “Cuenta”publicvoid cancelar(){}// Bien, no usa “cuenta”
...
}
Usando esta nomenclatura puedes refactorizar la clase Cuenta en una jerarquía sin renombrar sus miembros, y además puedes escribir código polimórfico con Cuenta.
Asociar Pedido con Factura
Hasta ahora, Pedido y Factura son exactamente iguales. Vamos a hacerles sus primeras extensiones, que va a ser asociar Pedido con Factura, como se muestra en el diagrama:
Solo necesitas añadir una referencia desde Pedido a Factura:
Por otra parte en Factura añadimos una colección de entidades Pedido:
packageorg.openxava.facturacion.modelo;importjava.util.*;// Añade este import para usar Collectionimportjavax.persistence.*;
@Entitypublicclass Factura extends DocumentoComercial {
@OneToMany(mappedBy="factura")privateCollection<Pedido> pedidos;// Colección de entidades Pedido añadidapublicCollection<Pedido> getPedidos(){return pedidos;}publicvoid setPedidos(Collection<Pedido> pedidos){this.pedidos = pedidos;}}
Después de escribir este código tan simple, ya puedes probar estas, recién creadas, relaciones. Abre tu navegador y explora los módulos Pedido y Factura. Fíjate como al final de la interfaz de usuario de Pedido tienes una referencia a Factura. El usuario puede usar esta referencia para asociar una factura al pedido actual. Por otra parte, si exploras el módulo Factura, verás una colección de pedidos al final. El usuario puede usarla para añadir pedidos a la factura actual.
Intenta añadir pedidos a la factura, y asociar una factura a un pedido. Funciona, aunque la interfaz de usuario es un poco fea, de momento.
Herencia de vistas
La herencia no solo sirve para reutilizar código Java y mapeos, sino también para reutilizar la definición de la interfaz de usuario, las definiciones @View. Esta sección muestra como funciona la herencia de vistas.
El atributo extendsView
Tanto Pedido como Factura usan una interfaz de usuario generada por defecto con todos sus miembros uno por cada línea. Nota como la @View que hemos declarado en DocumentoComercial no se ha heredado. Es decir, si no defines una vista para la entidad se genera una por defecto, la @View de la entidad padre no se usa. Como se muestra aquí:
@View(members = "a, b, c;")// Esta vista se usa para visualizar Padre, pero no para Hijopublicclass Padre { ... }publicclass Hijo extends Padre { ... }// Hijo se visualiza usando la vista// generada automáticamente, no la vista de Padre
Generalmente la vista de la entidad padre “tal cual” no es demasiado útil porque no contiene todas las propiedades de la entidad actual. Por tanto este comportamiento suele venir bien como comportamiento por defecto.
Aunque, en una entidad no trivial necesitas refinar la interfaz de usuario y quizás sea útil heredar (en lugar de copiar y pegar) la vista del padre. Puedes hacer esto usando el atributo extendsView en @View:
@View(members = "a, b, c;")// Esta vista sin nombre es la vista DEFAULTpublicclass Padre { ... }
@Views({
@View(name="A" members = "d", // Añade d a la vista heredada
extendsView = "super.DEFAULT"), // Extienda la vista por defecto de Padre
@View(name="B" members = "a, b, c; d")// La vista B es igual a la vista A})publicclass Hijo extends Padre { ... }// Hijo se visualiza usando la vista// generada automáticamente, no la vista de Padre
Usando extendsView los miembros a mostrar serán aquellos en la vista que extendemos más aquellos en members de la actual.
Vamos a usar esta característica para definir las vistas para DocumentoComercial, Pedido y Factura.
Vista para Factura usando herencia
Dado que la @View de DocumentoComercial no se hereda, la interfaz de usuario actual para Invoice es bastante fea: todos los miembros, uno por línea. Vamos a definir una interfaz de usuario mejor. Una vista parecida a la que ya teníamos, pero añadiendo una pestaña para pedidos, así:
Nota que ponemos todos los miembros de la parte de DocumentoComercial de Factura en la cabecera y la primera pestaña (datos), y la colección de pedidos en la otra pestaña. El siguiente código muestra la forma de definir esto sin herencia.
Puedes notar como todos los miembros, excepto la parte de pedidos, son comunes para todos los DocumentoComercial. Por lo tanto, vamos a mover esta parte común a DocumentoComercial y redefinir la vista usando herencia.
Quita el viejo @View de DocumentoComercial, y escribe esto:
@View(members=
"anyo, numero, fecha," + // Los miembros para la cabecera en una línea"datos {" + // Una pestaña 'datos' para los datos principales del documento"cliente;" +
"detalles;" +
"observaciones" +
"}")abstractpublicclass DocumentoComercial extends Identifiable {
Esta vista indica como distribuir los datos comunes para todos los documentos comerciales. Ahora podemos redefinir la vista de Factura a partir de esta:
importorg.openxava.annotations.*;// Añade este import para usar @View
@Entity
@View(extendsView="super.DEFAULT", // Extiende de la vista de DocumentoComercial
members="pedidos { pedidos }"// Añadimos pedidos dentro de una pestaña)publicclass Factura extends DocumentoComercial {
De esta forma declarar la vista para Factura es más corto, es más, la distribución común para Pedido, Factura y todos los demás posibles DocumentoComercial está en un único sitio, por tanto, si añades una nueva propiedad a DocumentoComercial solo has de tocar la vista de DocumentoComercial.
Vista para Pedido usando herencia
Ahora que tienes una vista adecuada para DocumentoComercial, declarar una vista para Pedido es lo más fácil del mundo. La vista que queremos contiene toda la información del pedido, y su factura asociada en otra pestaña:
Para obtener este resultado, puedes definir la vista de Pedido extendiendo la vista por defecto de DocumentoComercial, y añadiendo la referencia a factura, así:
importorg.openxava.annotations.*;// Añade este import para usar @View
@Entity
@View(extendsView="super.DEFAULT", // Extiende de la vista de DocumentoComercial
members="factura { factura } "// Añadimos factura dentro de una pestaña)publicclass Pedido extends DocumentoComercial {
Con esto conseguimos toda la información de DocumentoComercial más una pestaña con la factura.
Usar @ReferenceView y @CollectionView para refinar vistas
Queremos que cuando un pedido sea visualizado desde la interfaz de usuario de Factura la vista a usar sea simple, sin cliente ni factura, porque estos datos son redundantes en este caso:
Para obtener este resultado define una vista más simple en Pedido:
@Views({// Para declarar más de una vista
@View( extendsView="super.DEFAULT", // La vista por defecto
members="factura { factura } "),
@View( name="SinClienteNiFactura", // Una vista llamada SinClienteNiFactura
members= // que no incluye cliente ni factura"anyo, numero, fecha;" + // Ideal para ser usada desde Factura"detalles;" +
"observaciones")})publicclass Pedido extends DocumentoComercial {
Esta nueva vista definida en Pedido llamada SinClienteNiFactura puede ser referenciada desde Factura para visualizar elementos individuales de la colección pedidos usando @CollectionView:
@OneToMany(mappedBy="factura")
@CollectionView("SinClienteNiFactura")// Esta vista se usa para visualizar pedidosprivateCollection<Pedido> pedidos;
Y tan solo con este código la colección pedidos usará una vista más apropiada de Factura para visualizar elementos individuales.
Además, queremos que desde la interfaz de usuario de Pedido la factura no muestre el cliente y los pedidos, porque son datos redundantes en este caso. Para conseguirlo, vamos a definir una vista más simple en Factura:
@Views({// Para declarar más de una vista
@View( extendsView="super.DEFAULT", // La vista por defecto
members="pedidos { pedidos } "),
@View( name="SinClienteNiPedidos", // Una vista llamada SinClienteNiPedidos
members=// que no incluye cliente ni pedidos"anyo, numero, fecha;"+// Ideal para usarse desde Pedido"detalles;"+"observaciones")})publicclass Factura extends DocumentoComercial {
Esta nueva vista definida en Factura llamada SinClienteNiPedidos puede ser referenciada desde Pedido para visualizar la referencia a Factura usando @ReferenceView:
publicclass Pedido extends DocumentoComercial {
@ManyToOne
@ReferenceView("SinClienteNiPedidos")// Esta vista se usa para visualizar facturaprivate Factura factura;
...
}
Ahora la referencia factura será visualizada desde Pedido sin incluir el cliente y los pedidos, por lo que tendrás la interfaz de usuario más simple:
Herencia en las pruebas JUnit
Factura ha sido refactorizada para usar herencia, y también hemos usado herencia para añadir una nueva entidad, Pedido. Además, esta entidad Pedido tiene relación con Factura. Lo cual es una nueva funcionalidad, por ende has de probar todas estas nuevas características.
Dado que Factura y Pedido tienen bastantes cosas en común (la parte de DocumentoComercial) podemos refactorizar las pruebas para usar herencia, y así eludir el dañino “copiar y pegar” también en tu código de prueba.
Crear una prueba de módulo abstracta
Si examinas la prueba para crear una factura, en el método testCrear() de PruebaFactura. Puedes notar que probar la creación de una factura es exactamente igual que probar la creación de un pedido. Porque ambos tienen año, número, fecha, cliente, detalles y observaciones. Por tanto, aquí la herencia es una buena herramienta para la reutilización de código.
Vamos a renombrar PruebaFactura como PruebaDocumentoComercial, y entonces crearemos PruebaFactura y PruebaPedido a partir de él. Éste es el diagrama UML de esta idea:
Primero renombra tu actual clase PruebaFactura a PruebaDocumentoComercial, y después haz los cambios indicados en el siguiente código:
packageorg.openxava.facturacion.pruebas;importjava.text.*;importjava.util.*;importjavax.persistence.*;importorg.openxava.tests.*;importorg.openxava.util.*;importstaticorg.openxava.jpa.XPersistence.*;abstractpublicclass PruebaDocumentoComercial // Añade abstract a la definición de claseextends ModuleTestBase {privateString numero;public PruebaDocumentoComercial(String nombrePrueba,
String nombreModulo)// nombreModulo añadido como argumento en el constructor{super(nombrePrueba, "Facturacion", nombreModulo);// nombreModulo en vez de "Factura"}publicvoid testCrear()throwsException{ … }// Como el originalprivatevoid anyadirDetalles()throwsException{ … }// Como el originalprivateString getNumero(){if(numero == null){Query query = getManager().
createQuery("select max(f.numero) "
+ "from DocumentoComercial f "// Factura cambiada por DocumentoComercial
+ "where f.anyo = :anyo");
query.setParameter("anyo", Dates.getYear(newDate()));Integer ultimoNumero = (Integer) query.getSingleResult();if(ultimoNumero == null) ultimoNumero = 0;
numero = Integer.toString(ultimoNumero + 1);}return numero;}privatevoid borrar()throwsException{ … }// Como originalprivatevoid verificarCreado()throwsException{ … }// Como originalprivatevoid grabar()throwsException{ … }// Como originalprivatevoid ponerOtrasPropiedades()throwsException{ … }// Como originalprivatevoid escogerCliente()throwsException{ … }// Como originalprivatevoid verificarValoresDefecto()throwsException{ … }// Como originalprivateString getAnyoActual(){ … }// Como originalprivateString getFechaActual(){ … }// Como original}
Como ves has tenido que hacer unos pocos cambios para adaptar PruebaDocumentoComercial. Primero, la has declarado abstracta, de esta forma esta clase no es ejecutada por Eclipse como una prueba JUnit, es solo válida como clase base para crear pruebas, pero ella misma no es una prueba.
Otro cambio importante nos lo encontramos en el constructor, donde ahora tienes nombreModulo en vez de “Factura”, así puedes usar esta prueba para Pedido, Factura o cualquier otro módulo que quieras. El otro cambio es un simple detalle: has de cambiar “Factura” por “DocumentoComercial” en la consulta para obtener el siguiente número.
Ahora ya tienes una clase base lista para crear los módulos de prueba para Pedido y Factura. Hagámoslo sin más dilación.
Prueba base abstracta para crear pruebas de módulo concretas
Crear tu primera versión para PruebaFactura y PruebaPedido es simplemente extender de PruebaDocumentoComercial. Nada más. Mira el código de PruebaFactura:
Ejecuta estas dos prueba y verás como testCrear(), heredado de PruebaDocumentoComercial, se ejecuta en ambos casos, contra su módulo correspondiente. Con esto estamos probando el comportamiento común para Pedido y Factura. Probemos ahora la funcionalidad particular de cada uno.
Añadir nuevas pruebas a las pruebas de módulo extendidas
Hasta ahora hemos probado como crear una factura y un pedido. En esta sección probaremos como añadir pedidos a una factura en el módulo Factura, y como establecer la factura a un pedido en el módulo Pedido.
Para probar como añadir un pedido a una factura añade el método testAnyadirPedidos() a PruebaFactura:
publicvoid testAnyadirPedidos()throwsException{
login("admin", "admin");
assertListNotEmpty();// Esta prueba confía en que ya existen facturas
execute("List.orderBy", "property=numero");// Para usar siempre el mismo pedido
execute("Mode.detailAndFirst");// Va al modo detalle editando la primera factura
execute("Sections.change", "activeSection=1");// Cambia a la pestaña 1
assertCollectionRowCount("pedidos", 0);// Esta factura no tiene pedidos
execute("Collection.add", // Pulsa el botón para añadir un nuevo pedido, esto te lleva"viewObject=xava_view_section1_pedidos");// a la lista de pedidos
execute("AddToCollection.add", "row=0");// Escoge el primer pedido de la lista
assertCollectionRowCount("pedidos", 1);// El pedido se ha añadido a la factura
checkRowCollection("pedidos", 0);// Marca el pedido, para borrarlo
execute("Collection.removeSelected", // Borra el pedido recién añadido"viewObject=xava_view_section1_pedidos");
assertCollectionRowCount("pedidos", 0);// El pedido ha sido borrado}
En este caso asumimos que hay al menos una factura, y que la primera factura de la lista no tiene pedidos. Antes de ejecutar esta prueba, si no tienes facturas todavía, crea una sin pedidos, o si ya tienes facturas, asegúrate de que la primera no tiene pedidos.
Para probar como asignar una factura a un pedido añade el método testPonerFactura() a PruebaPedido:
publicvoid testPonerFactura()throwsException{
login("admin", "admin");
assertListNotEmpty();// Esta prueba confía en que existen pedidos
execute("Mode.detailAndFirst");// Va a modo detalle editando la primera factura
execute("Sections.change", "activeSection=1");// Cambia a la pestaña 1
assertValue("factura.numero", "");// Este pedido todavía no tiene
assertValue("factura.anyo", "");// una factura asignada
execute("Reference.search", // Pulsa en el botón para buscar la factura, esto te"keyProperty=factura.anyo");// lleva a la lista de facturasString anyo = getValueInList(0, "anyo");// Memoriza el año y el número deString numero = getValueInList(0, "numero");// la primera factura de la lista
execute("ReferenceSearch.choose", "row=0");// Escoge la primera factura
assertValue("factura.anyo", anyo);// Al volver al detalle del pedido verificamos
assertValue("factura.numero", numero);// que la factura ha sido seleccionada}
En este caso asumimos que hay al menos un pedido, y el primer pedido de la lista no tiene factura. Antes de ejecutar esta prueba, si no tienes pedidos, crea uno sin factura, o si ya tienes pedidos, asegúrate de que el primero no tiene factura.
Con esto ya tienes tus pruebas listas. Ejecútalas, y obtendrás el siguiente resultado:
Fíjate que la prueba base PruebaDocumentoComercial no se muestra porque es abstracta. Y testCrear() de PruebaDocumentoComercial se ejecuta para PruebaFactura y PruebaPedido.
No solo has adaptado tu código de pruebas al nuevo código de Facturacion, sino que también has aprendido como usar herencia en el mismo código de pruebas.
Resumen
Esta lección te ha mostrado algunos ejemplos prácticos sobre como usar herencia con Java y JPA para simplificar tu código. Hemos usado la configuración por defecto de JPA para la herencia, aunque puedes refinar el comportamiento de JPA para la herencia con anotaciones como @Inheritance, @DiscriminatorColumn, @DiscriminatorValue, etc. Para aprender más acerca de la herencia en JPA puedes leer la documentación del apéndice B.
Table of Contents
Lección 4: Herencia
La herencia es una forma práctica de reutilizar el código en el mundo de la orientación a objetos. Usar herencia con JPA y OpenXava es tan fácil como hacerlo con puro Java. Vamos a usar la herencia para quitar el código repetitivo y aburrido, como la definición de los UUID. También, añadiremos una nueva entidad, Pedido, y usaremos la herencia para hacerlo con muy poco código. Además, aprenderás cuán práctico es usar herencia incluso para código de pruebas.Heredar de una superclase mapeada
Las clases Autor, Categoria y Factura tienen algo de código en común. Este código es la definición del campo oid:Este código es exactamente el mismo para todas estas clases. Ya sabes que copiar y pegar es un pecado mortal, por eso tenemos que buscar una forma de quitar este código repetido, y así evitar ir al infierno.
Una solución elegante en este caso es usar herencia. JPA permite varias formas de herencia. Una de ellas es heredar de una superclase mapeada. Una superclase mapeada es una clase Java con anotaciones de mapeo JPA, pero no es una entidad en sí. Su único objetivo es ser usada como clase base para definir entidades. Usemos una, y verás su utilidad rápidamente.
Primero, movemos el código común a una clase marcada como @MappedSuperclass. La llamamos Identificable:
Ahora puedes definir las entidades Autor, Categoria y Factura de una manera más sucinta. Para ver un ejemplo aquí tienes el nuevo código para Categoria:
La refactorización es extremadamente simple. Categoria ahora desciende de Identificable y hemos quitado el campo oid y los métodos getOid() y setOid(). De esta forma, no sólo tu código es más corto, sino también más elegante, porque estás declarando tu clase como identificable (el qué, no el cómo), y has quitado de tu clase de negocio un código que era un tanto técnico.
Ahora, puedes aplicar esta misma refactorización a las entidades Autor y Factura. Además, a partir de ahora extenderás la mayoría de tus entidades de la superclase mapeada Identificable.
Hemos creado nuestra propia clase Identificable para ver las ventajas de usar superclases mapeadas, sin embargo OpenXava te provee una clase Identifiable lista para usar que puedes encontrar en el paquete org.openxava.model. Por tanto, en tu próximo proyecto no has de escribir la clase Identificable otra vez, simplemente usa la incluida en OpenXava.
Has aprendido, pues, que una superclase mapeada es una clase normal y corriente con anotaciones de mapeo JPA que puedes usar como clase base para tus entidades. También has aprendido como usar una superclase mapeada para simplificar tu código.
Herencia de entidades
Una entidad puede heredar de otra entidad. Esta herencia de entidades es una herramienta muy útil para simplificar tu modelo. Vamos a usarla para añadir una nueva entidad, Pedido, a tu aplicación Facturacion.Nueva entidad Pedido
Queremos añadir un nuevo concepto a la aplicación Facturacion: pedido. Mientras que una factura es algo que quieres cobrar a tu cliente, un pedido es algo que tu cliente te ha solicitado. Estos dos conceptos están fuertemente unidos, porque cobrarás por las cosas que tu cliente te ha pedido, y tú le has servido.Sería interesante poder tratar pedidos en tu aplicación, y asociar estos pedidos con sus correspondientes facturas. Tal como muestra el siguiente diagrama UML:
Y con código Java:
Es decir, una factura tiene varios pedidos, y un pedido puede referenciar a una factura. Fíjate como no usamos inicialización vaga (lazy fetching) para la referencia factura (1), esto es por un bug de Hibernate cuando la referencia
contiene la relación bidireccional (es decir, es la referencia declarada en el atributo mappedBy del @OneToMany).
¿Cómo es Pedido? Bien, tiene un cliente, unas líneas de detalle con producto y cantidad, un año y un número. Algo así como esto:
Curiosamente, este diagrama UML es idéntico al diagrama de Factura. Es decir, para crear tu entidad Pedido puedes copiar y pegar la clase Factura, y asunto zanjado. Pero, ¡espera un momento! ¿“Copiar y pegar” no era un pecado mortal? Hemos de encontrar una forma de reutilizar Factura para Pedido.
CommercialDocument como entidad abstracta
Una manera práctica de reutilizar el código de Factura para Pedido es usando herencia, además es una excusa perfecta para aprender lo fácil que es usar la herencia con JPA y OpenXava.En la mayoría de las culturas orientadas a objetos has de observar el precepto sagrado es un. Esto significa que no podemos hacer que Factura herede de Pedido, porque una Factura no es un Pedido, y por la misma regla no podemos hacer que Pedido descienda de Factura. La solución para este caso es crear una clase base para ambos, Pedido y Factura. Llamaremos a esta clase DocumentoComercial.
Aquí tienes el diagrama UML para DocumentoComercial:
Y aquí tienes la misma idea expresada con Java:
Empecemos a refactorizar el código actual. Primero, renombra (usando Refactor > Rename) Factura como DocumentoComercial. Después, edita el código de DocumentoComercial para declararla como una clase abstracta, así:
Queremos crear instancias de Factura y Pedido, pero no queremos crear instancias de DocumentoComercial directamente, por eso la declaramos abstracta.
Factura refactorizada para usar herencia
Ahora, has de crear la entidad Factura extendiéndola de DocumentoComercial. Puedes ver el nuevo código de Factura:Factura tiene ahora un código bastante breve, de hecho el cuerpo de la clase está, por ahora, vacío.
Este nuevo código necesita un esquema de base de datos ligeramente diferente, ahora las facturas y los pedidos se almacenarán en la misma tabla (la tabla DocumentoComercial) usando una columna discriminador. Por tanto has de borrar las viejas tablas ejecutando las siguientes sentencias SQL:
Para ejecutar estas sentencias SQL ejecuta start-manager-hsqldb.bat (o el .sh correspendiente si usas Linux/Mac) de openxava/tomcat/bin. Mostrará un diálogo de configuración, pon la URL de la base de datos (jdbc:hsqldb:hsql://localhost:1666) y acéptalo:
Después:
Ya puedes ejecutar el módulo Factura y verlo funcionando en tu navegador. Lanza también PruebaFactura. Tiene que salirte verde.
Crear Pedido usando herencia
Gracias a DocumentoComercial el código para Pedido es más sencillo que el mecanismo de un sonajero:Después de escribir la clase Pedido, aunque de momento esté vacía, ya puedes usar el módulo Pedido desde tu navegador. Sí, a partir de ahora crear una nueva entidad con una estructura parecida a Factura, es decir cualquier entidad para un documento comercial, es simple y rápido. Un buen uso de la herencia es una forma elegante de tener un código más simple.
El módulo Pedido funciona perfectamente, pero tiene un pequeño problema. El nuevo número de pedido lo calcula a partir del último número de factura, no de pedido. Esto es así porque el calculador para el siguiente número lee de la entidad Factura. Un solución obvia es mover la definición de la propiedad numero de DocumentoComercial a Factura y Pedido. Aunque, no lo vamos a hacer así, porque en la siguiente lección refinaremos la forma de calcular el número de documento, de momento simplemente haremos un pequeño ajuste en el código actual para que no falle. Edita la clase CalculadorSiguienteNumeroParaAnyo y en la consulta cambia “Factura” por “DocumentoComercial”, dejando el método calculate(), así:
Ahora buscamos el número máximo de cualquier tipo de documento comercial del año para calcular el nuevo número, por lo tanto la numeración es compartida para todos los tipos de documentos. Esto lo mejoraremos en la siguiente lección para separar la numeración para facturas y pedidos; y para tener un mejor soporte de entornos multiusuario.
Convención de nombres y herencia
Fíjate que no has necesitado cambiar el nombre de ninguna propiedad de Factura para hacer la refactorización. Esto es por el siguiente principio práctico: No uses el nombre de clase en los nombres de miembro, por ejemplo, dentro de una clase Cuenta no uses la palabra “Cuenta” en ningún método o propiedad:Usando esta nomenclatura puedes refactorizar la clase Cuenta en una jerarquía sin renombrar sus miembros, y además puedes escribir código polimórfico con Cuenta.
Asociar Pedido con Factura
Hasta ahora, Pedido y Factura son exactamente iguales. Vamos a hacerles sus primeras extensiones, que va a ser asociar Pedido con Factura, como se muestra en el diagrama:Solo necesitas añadir una referencia desde Pedido a Factura:
Por otra parte en Factura añadimos una colección de entidades Pedido:
Después de escribir este código tan simple, ya puedes probar estas, recién creadas, relaciones. Abre tu navegador y explora los módulos Pedido y Factura. Fíjate como al final de la interfaz de usuario de Pedido tienes una referencia a Factura. El usuario puede usar esta referencia para asociar una factura al pedido actual. Por otra parte, si exploras el módulo Factura, verás una colección de pedidos al final. El usuario puede usarla para añadir pedidos a la factura actual.
Intenta añadir pedidos a la factura, y asociar una factura a un pedido. Funciona, aunque la interfaz de usuario es un poco fea, de momento.
Herencia de vistas
La herencia no solo sirve para reutilizar código Java y mapeos, sino también para reutilizar la definición de la interfaz de usuario, las definiciones @View. Esta sección muestra como funciona la herencia de vistas.El atributo extendsView
Tanto Pedido como Factura usan una interfaz de usuario generada por defecto con todos sus miembros uno por cada línea. Nota como la @View que hemos declarado en DocumentoComercial no se ha heredado. Es decir, si no defines una vista para la entidad se genera una por defecto, la @View de la entidad padre no se usa. Como se muestra aquí:Generalmente la vista de la entidad padre “tal cual” no es demasiado útil porque no contiene todas las propiedades de la entidad actual. Por tanto este comportamiento suele venir bien como comportamiento por defecto.
Aunque, en una entidad no trivial necesitas refinar la interfaz de usuario y quizás sea útil heredar (en lugar de copiar y pegar) la vista del padre. Puedes hacer esto usando el atributo extendsView en @View:
Usando extendsView los miembros a mostrar serán aquellos en la vista que extendemos más aquellos en members de la actual.
Vamos a usar esta característica para definir las vistas para DocumentoComercial, Pedido y Factura.
Vista para Factura usando herencia
Dado que la @View de DocumentoComercial no se hereda, la interfaz de usuario actual para Invoice es bastante fea: todos los miembros, uno por línea. Vamos a definir una interfaz de usuario mejor. Una vista parecida a la que ya teníamos, pero añadiendo una pestaña para pedidos, así:Nota que ponemos todos los miembros de la parte de DocumentoComercial de Factura en la cabecera y la primera pestaña (datos), y la colección de pedidos en la otra pestaña. El siguiente código muestra la forma de definir esto sin herencia.
Puedes notar como todos los miembros, excepto la parte de pedidos, son comunes para todos los DocumentoComercial. Por lo tanto, vamos a mover esta parte común a DocumentoComercial y redefinir la vista usando herencia.
Quita el viejo @View de DocumentoComercial, y escribe esto:
Esta vista indica como distribuir los datos comunes para todos los documentos comerciales. Ahora podemos redefinir la vista de Factura a partir de esta:
De esta forma declarar la vista para Factura es más corto, es más, la distribución común para Pedido, Factura y todos los demás posibles DocumentoComercial está en un único sitio, por tanto, si añades una nueva propiedad a DocumentoComercial solo has de tocar la vista de DocumentoComercial.
Vista para Pedido usando herencia
Ahora que tienes una vista adecuada para DocumentoComercial, declarar una vista para Pedido es lo más fácil del mundo. La vista que queremos contiene toda la información del pedido, y su factura asociada en otra pestaña:Para obtener este resultado, puedes definir la vista de Pedido extendiendo la vista por defecto de DocumentoComercial, y añadiendo la referencia a factura, así:
Con esto conseguimos toda la información de DocumentoComercial más una pestaña con la factura.
Usar @ReferenceView y @CollectionView para refinar vistas
Queremos que cuando un pedido sea visualizado desde la interfaz de usuario de Factura la vista a usar sea simple, sin cliente ni factura, porque estos datos son redundantes en este caso:Para obtener este resultado define una vista más simple en Pedido:
Esta nueva vista definida en Pedido llamada SinClienteNiFactura puede ser referenciada desde Factura para visualizar elementos individuales de la colección pedidos usando @CollectionView:
Y tan solo con este código la colección pedidos usará una vista más apropiada de Factura para visualizar elementos individuales.
Además, queremos que desde la interfaz de usuario de Pedido la factura no muestre el cliente y los pedidos, porque son datos redundantes en este caso. Para conseguirlo, vamos a definir una vista más simple en Factura:
Esta nueva vista definida en Factura llamada SinClienteNiPedidos puede ser referenciada desde Pedido para visualizar la referencia a Factura usando @ReferenceView:
Ahora la referencia factura será visualizada desde Pedido sin incluir el cliente y los pedidos, por lo que tendrás la interfaz de usuario más simple:
Herencia en las pruebas JUnit
Factura ha sido refactorizada para usar herencia, y también hemos usado herencia para añadir una nueva entidad, Pedido. Además, esta entidad Pedido tiene relación con Factura. Lo cual es una nueva funcionalidad, por ende has de probar todas estas nuevas características.Dado que Factura y Pedido tienen bastantes cosas en común (la parte de DocumentoComercial) podemos refactorizar las pruebas para usar herencia, y así eludir el dañino “copiar y pegar” también en tu código de prueba.
Crear una prueba de módulo abstracta
Si examinas la prueba para crear una factura, en el método testCrear() de PruebaFactura. Puedes notar que probar la creación de una factura es exactamente igual que probar la creación de un pedido. Porque ambos tienen año, número, fecha, cliente, detalles y observaciones. Por tanto, aquí la herencia es una buena herramienta para la reutilización de código.Vamos a renombrar PruebaFactura como PruebaDocumentoComercial, y entonces crearemos PruebaFactura y PruebaPedido a partir de él. Éste es el diagrama UML de esta idea:
Primero renombra tu actual clase PruebaFactura a PruebaDocumentoComercial, y después haz los cambios indicados en el siguiente código:
Como ves has tenido que hacer unos pocos cambios para adaptar PruebaDocumentoComercial. Primero, la has declarado abstracta, de esta forma esta clase no es ejecutada por Eclipse como una prueba JUnit, es solo válida como clase base para crear pruebas, pero ella misma no es una prueba.
Otro cambio importante nos lo encontramos en el constructor, donde ahora tienes nombreModulo en vez de “Factura”, así puedes usar esta prueba para Pedido, Factura o cualquier otro módulo que quieras. El otro cambio es un simple detalle: has de cambiar “Factura” por “DocumentoComercial” en la consulta para obtener el siguiente número.
Ahora ya tienes una clase base lista para crear los módulos de prueba para Pedido y Factura. Hagámoslo sin más dilación.
Prueba base abstracta para crear pruebas de módulo concretas
Crear tu primera versión para PruebaFactura y PruebaPedido es simplemente extender de PruebaDocumentoComercial. Nada más. Mira el código de PruebaFactura:Y PruebaPedido:
Ejecuta estas dos prueba y verás como testCrear(), heredado de PruebaDocumentoComercial, se ejecuta en ambos casos, contra su módulo correspondiente. Con esto estamos probando el comportamiento común para Pedido y Factura. Probemos ahora la funcionalidad particular de cada uno.
Añadir nuevas pruebas a las pruebas de módulo extendidas
Hasta ahora hemos probado como crear una factura y un pedido. En esta sección probaremos como añadir pedidos a una factura en el módulo Factura, y como establecer la factura a un pedido en el módulo Pedido.Para probar como añadir un pedido a una factura añade el método testAnyadirPedidos() a PruebaFactura:
En este caso asumimos que hay al menos una factura, y que la primera factura de la lista no tiene pedidos. Antes de ejecutar esta prueba, si no tienes facturas todavía, crea una sin pedidos, o si ya tienes facturas, asegúrate de que la primera no tiene pedidos.
Para probar como asignar una factura a un pedido añade el método testPonerFactura() a PruebaPedido:
En este caso asumimos que hay al menos un pedido, y el primer pedido de la lista no tiene factura. Antes de ejecutar esta prueba, si no tienes pedidos, crea uno sin factura, o si ya tienes pedidos, asegúrate de que el primero no tiene factura.
Con esto ya tienes tus pruebas listas. Ejecútalas, y obtendrás el siguiente resultado:
Fíjate que la prueba base PruebaDocumentoComercial no se muestra porque es abstracta. Y testCrear() de PruebaDocumentoComercial se ejecuta para PruebaFactura y PruebaPedido.
No solo has adaptado tu código de pruebas al nuevo código de Facturacion, sino que también has aprendido como usar herencia en el mismo código de pruebas.
Resumen
Esta lección te ha mostrado algunos ejemplos prácticos sobre como usar herencia con Java y JPA para simplificar tu código. Hemos usado la configuración por defecto de JPA para la herencia, aunque puedes refinar el comportamiento de JPA para la herencia con anotaciones como @Inheritance, @DiscriminatorColumn, @DiscriminatorValue, etc. Para aprender más acerca de la herencia en JPA puedes leer la documentación del apéndice B.Descargar código fuente de esta lección
¿Problemas con la lección? Pregunta en el foro ¿Ha ido bien? Ve a la lección 5